/*
 * $Id: rx.c,v 1.1.2.8 Broadcom SDK $
 * 
 * $Copyright: Copyright 2011 Broadcom Corporation.
 * This program is the proprietary software of Broadcom Corporation
 * and/or its licensors, and may only be used, duplicated, modified
 * or distributed pursuant to the terms and conditions of a separate,
 * written license agreement executed between you and Broadcom
 * (an "Authorized License").  Except as set forth in an Authorized
 * License, Broadcom grants no license (express or implied), right
 * to use, or waiver of any kind with respect to the Software, and
 * Broadcom expressly reserves all rights in and to the Software
 * and all intellectual property rights therein.  IF YOU HAVE
 * NO AUTHORIZED LICENSE, THEN YOU HAVE NO RIGHT TO USE THIS SOFTWARE
 * IN ANY WAY, AND SHOULD IMMEDIATELY NOTIFY BROADCOM AND DISCONTINUE
 * ALL USE OF THE SOFTWARE.  
 *  
 * Except as expressly set forth in the Authorized License,
 *  
 * 1.     This program, including its structure, sequence and organization,
 * constitutes the valuable trade secrets of Broadcom, and you shall use
 * all reasonable efforts to protect the confidentiality thereof,
 * and to use this information only in connection with your use of
 * Broadcom integrated circuit products.
 *  
 * 2.     TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS
 * PROVIDED "AS IS" AND WITH ALL FAULTS AND BROADCOM MAKES NO PROMISES,
 * REPRESENTATIONS OR WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY,
 * OR OTHERWISE, WITH RESPECT TO THE SOFTWARE.  BROADCOM SPECIFICALLY
 * DISCLAIMS ANY AND ALL IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY,
 * NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF VIRUSES,
 * ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR
 * CORRESPONDENCE TO DESCRIPTION. YOU ASSUME THE ENTIRE RISK ARISING
 * OUT OF USE OR PERFORMANCE OF THE SOFTWARE.
 * 
 * 3.     TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL
 * BROADCOM OR ITS LICENSORS BE LIABLE FOR (i) CONSEQUENTIAL,
 * INCIDENTAL, SPECIAL, INDIRECT, OR EXEMPLARY DAMAGES WHATSOEVER
 * ARISING OUT OF OR IN ANY WAY RELATING TO YOUR USE OF OR INABILITY
 * TO USE THE SOFTWARE EVEN IF BROADCOM HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES; OR (ii) ANY AMOUNT IN EXCESS OF
 * THE AMOUNT ACTUALLY PAID FOR THE SOFTWARE ITSELF OR USD 1.00,
 * WHICHEVER IS GREATER. THESE LIMITATIONS SHALL APPLY NOTWITHSTANDING
 * ANY FAILURE OF ESSENTIAL PURPOSE OF ANY LIMITED REMEDY.$
 */
#if defined(INCLUDE_PTP)

#ifdef BCM_HIDE_DISPATCHABLE
#undef BCM_HIDE_DISPATCHABLE
#endif

#include <soc/defs.h>
#include <soc/drv.h>

#include <sal/appl/io.h>

#include <bcm/pkt.h>
#include <bcm/tx.h>
#include <bcm/rx.h>
#include <bcm/error.h>
#include <bcm/ptp.h>

#include <bcm_int/esw/rx.h>
#include <bcm_int/esw/tx.h>
#include <bcm_int/esw_dispatch.h>

#if defined(BCM_KATANA_SUPPORT)
#include <bcm_int/esw/katana.h>
#endif

#define PTP_RX_PACKET_MIN_SIZE_OCTETS     (14)
#define PTP_RX_TUNNEL_MSG_MIN_SIZE_OCTETS (8)
#define PTP_RX_EVENT_MSG_MIN_SIZE_OCTETS  (2)

/* PTP clock Rx data. */
typedef struct _bcm_ptp_clock_rx_data_s {
    sal_sem_t response_ready;            
    uint8 *response_data;
    int response_len;
} _bcm_ptp_clock_rx_data_t;

/* Stack PTP Rx data arrays. */
typedef struct _bcm_ptp_stack_rx_array_s {
    _bcm_ptp_memstate_t memstate;
    
    bcm_mac_t host_mac;
    bcm_mac_t top_mac;
    
    _bcm_ptp_clock_rx_data_t *clock_data;
} _bcm_ptp_stack_rx_array_t;

/* Unit PTP Rx data arrays. */
typedef struct _bcm_ptp_unit_rx_array_s {
    _bcm_ptp_memstate_t memstate;
       
    bcm_ptp_cb management_cb;
    bcm_ptp_cb event_cb;
    bcm_ptp_cb signal_cb;
    bcm_ptp_cb fault_cb;
    uint8      *management_user_data;
    uint8      *event_user_data;
    uint8      *signal_user_data;
    uint8      *fault_user_data;
            
    _bcm_ptp_stack_rx_array_t *stack_array;
} _bcm_ptp_unit_rx_array_t;

static const _bcm_ptp_clock_rx_data_t rx_default;
static _bcm_ptp_unit_rx_array_t unit_rx_array[BCM_MAX_NUM_UNITS];

static bcm_rx_t _bcm_ptp_rx_callback(
    int unit, 
    bcm_pkt_t *pkt, 
    void *cookie);

#if 0 /* Unused. */
static int _bcm_ptp_rx_message_source_port_get(
    uint8 *message, 
    uint16 *src_port);
#endif /* Unused. */

static int _bcm_ptp_rx_message_destination_port_get(
    uint8 *message, 
    uint16 *dest_port);

#if 0 /* Unused. */
static int _bcm_ptp_rx_message_type_get(
    uint8 *message, 
    _bcmPTPmessageType_t *message_type);
#endif /* Unused. */

static int _bcm_ptp_rx_message_source_clock_identity_get(
    uint8 *message, 
    bcm_ptp_clock_identity_t *clock_identity);

static int _bcm_ptp_rx_message_target_clock_identity_get(
    uint8 *message, 
    bcm_ptp_clock_identity_t *clock_identity);

static int _bcm_ptp_rx_message_length_get(
    uint8 *message,
    uint16 *message_len);

STATIC int _bcm_ptp_event_handler_default(
    int unit,
    bcm_ptp_stack_id_t ptp_id,
    uint32 flags,
    bcm_ptp_cb_type_t type,
    uint32 length,
    uint8 *data,
    void *user_data);

STATIC int _bcm_ptp_management_handler_default(
    int unit,
    bcm_ptp_stack_id_t ptp_id,
    uint32 flags,
    bcm_ptp_cb_type_t type,
    uint32 length,
    uint8 *data,
    void *user_data);

STATIC int _bcm_ptp_signal_handler_default(
    int unit,
    bcm_ptp_stack_id_t ptp_id,
    uint32 flags,
    bcm_ptp_cb_type_t type,
    uint32 length,
    uint8 *data,
    void *user_data);

STATIC const char* _bcm_ptp_port_state_description(
    _bcm_ptp_port_state_t state);

STATIC const char* _bcm_ptp_ieee1588_warn_reason_description(
    _bcm_ptp_ieee1588_warn_reason_t reason);

STATIC const char* _bcm_ptp_servo_state_description(
    _bcm_ptp_fll_state_t state); 

STATIC const char* _bcm_ptp_pps_in_state_description(
    _bcm_ptp_pps_in_state_t state);

/*
 * Function:
 *      _bcm_ptp_rx_init
 * Purpose:
 *      Initialize the PTP Rx framework and data of a unit.
 * Parameters:
 *      unit - (IN) Unit number.
 * Returns:
 *      BCM_E_XXX
 * Notes:
 */
int 
_bcm_ptp_rx_init(
    int unit)
{
    int rv = BCM_E_UNAVAIL;
    _bcm_ptp_stack_rx_array_t *stack_p;
     
    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, PTP_STACK_ID_DEFAULT, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        return rv;   
    }
                           
    /* TODO: Fully implement logic to handle repeated initialization. */
    stack_p = sal_alloc(PTP_MAX_STACKS_PER_UNIT*
                        sizeof(_bcm_ptp_stack_rx_array_t),"Unit Rx arrays");

    if (!stack_p) {
        unit_rx_array[unit].memstate = PTP_MEMSTATE_FAILURE;
        return BCM_E_MEMORY;
    }

    unit_rx_array[unit].stack_array = stack_p;
    unit_rx_array[unit].memstate = PTP_MEMSTATE_INITIALIZED;

    unit_rx_array[unit].management_cb = NULL;
    unit_rx_array[unit].event_cb = NULL;
    unit_rx_array[unit].signal_cb = NULL;
    unit_rx_array[unit].fault_cb = NULL;
        
    unit_rx_array[unit].management_user_data = NULL;
    unit_rx_array[unit].event_user_data = NULL;
    unit_rx_array[unit].signal_user_data = NULL;
    unit_rx_array[unit].fault_user_data = NULL;
        
    /* 
     * Add default callback functions for event, tunneled PTP management 
     * messages, and tunneled PTP signaling messages. 
     */
    if (BCM_FAILURE(rv = _bcm_ptp_register_event_callback(unit, 
            _bcm_ptp_event_handler_default, NULL))) {
        PTP_ERROR_FUNC("_bcm_ptp_register_event_callback()");
    }

    if (BCM_FAILURE(rv = _bcm_ptp_register_management_callback(unit, 
            _bcm_ptp_management_handler_default, NULL))) {
        PTP_ERROR_FUNC("_bcm_ptp_register_management_callback()");
    }

    if (BCM_FAILURE(rv = _bcm_ptp_register_signal_callback(unit, 
            _bcm_ptp_signal_handler_default, NULL))) {
        PTP_ERROR_FUNC("_bcm_ptp_register_signal_callback()");
    }

    if (!bcm_rx_active(unit)) {
        if (BCM_FAILURE(rv = bcm_rx_cfg_init(unit))) {
            PTP_ERROR_FUNC("bcm_rx_cfg_init()");
            return rv;
        }

        if (BCM_FAILURE(rv = bcm_rx_start(unit, NULL))) {
            PTP_ERROR_FUNC("bcm_rx_start()");
            return rv;
        }
    }

#ifndef CUSTOMER_CALLBACK
    if (BCM_FAILURE(rv = bcm_rx_register(unit, "BCM_PTP_Rx", 
            _bcm_ptp_rx_callback, BCM_RX_PRIO_MAX, NULL, BCM_RCO_F_ALL_COS))) {
        PTP_ERROR_FUNC("bcm_rx_register()");
        return rv;
    }
#endif /* CUSTOMER_CALLBACK */
    return BCM_E_NONE;
}

/*
 * Function:
 *      _bcm_ptp_rx_stack_create
 * Purpose:
 *      Create the PTP Rx data of a PTP stack.
 * Parameters:
 *      unit     - (IN) Unit number.
 *      ptp_id   - (IN) PTP stack ID.
 *      host_mac - (IN) Host MAC address.
 *      top_mac  - (IN) ToP MAC address.
 * Returns:
 *      BCM_E_XXX
 * Notes:
 */
int 
_bcm_ptp_rx_stack_create(
    int unit, 
    bcm_ptp_stack_id_t ptp_id,
    bcm_mac_t *host_mac,
    bcm_mac_t *top_mac)
{
    int rv = BCM_E_UNAVAIL;  
    _bcm_ptp_clock_rx_data_t *data_p;
        
    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, ptp_id, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        PTP_ERROR_FUNC("_bcm_ptp_function_precheck()");
        return rv;   
    }
    
    if (unit_rx_array[unit].memstate != PTP_MEMSTATE_INITIALIZED) {
        PTP_ERROR("_bcm_ptp_rx_stack_create(): memory state\n");
        return BCM_E_UNAVAIL;
    }
        
    /* TODO: Fully implement logic to handle repeated creation. */
    data_p = sal_alloc(PTP_MAX_CLOCK_INSTANCES*
                       sizeof(_bcm_ptp_clock_rx_data_t),
                       "PTP stack Rx array");

    if (!data_p) {
        unit_rx_array[unit].stack_array[ptp_id].memstate = 
            PTP_MEMSTATE_FAILURE;
        return BCM_E_MEMORY;
    }

    unit_rx_array[unit].stack_array[ptp_id].clock_data = data_p;
    unit_rx_array[unit].stack_array[ptp_id].memstate = 
        PTP_MEMSTATE_INITIALIZED;
    
    sal_memcpy(unit_rx_array[unit].stack_array[ptp_id].host_mac, host_mac, 
               sizeof(bcm_mac_t));

    sal_memcpy(unit_rx_array[unit].stack_array[ptp_id].top_mac, top_mac, 
               sizeof(bcm_mac_t));

    return rv;
}

/*
 * Function:
 *      _bcm_ptp_rx_clock_create
 * Purpose:
 *      Create the PTP Rx data of a PTP clock.
 * Parameters:
 *      unit      - (IN) Unit number.
 *      ptp_id    - (IN) PTP stack ID.
 *      clock_num - (IN) PTP clock number.
 * Returns:
 *      BCM_E_XXX
 * Notes:
 */
int 
_bcm_ptp_rx_clock_create(
    int unit, 
    bcm_ptp_stack_id_t ptp_id,
    int clock_num)
{
    int rv = BCM_E_UNAVAIL;
             
    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, ptp_id, clock_num,
            PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        return rv;   
    }
    
    if ((unit_rx_array[unit].memstate != PTP_MEMSTATE_INITIALIZED) ||
        (unit_rx_array[unit].stack_array[ptp_id].memstate != 
            PTP_MEMSTATE_INITIALIZED)) {
        return BCM_E_UNAVAIL;
    }
    
    unit_rx_array[unit].stack_array[ptp_id]
                       .clock_data[clock_num] = rx_default;
    
    unit_rx_array[unit].stack_array[ptp_id]
                       .clock_data[clock_num].response_ready = 
        sal_sem_create("BCM_PTP_resp", sal_sem_BINARY, 0);
        
    return rv;
}

/*
 * Function:
 *      _bcm_ptp_rx_response_flush
 * Purpose:
 *      Flush prior Rx response.
 * Parameters:
 *      unit       - (IN)  Unit number.
 *      ptp_id     - (IN)  PTP stack ID.
 *      clock_num  - (IN)  PTP clock number.
 * Returns:
 *      BCM_E_XXX
 * Notes:
 */
int 
_bcm_ptp_rx_response_flush(
    int unit,
    bcm_ptp_stack_id_t ptp_id, 
    int clock_num)
{
    int rv = BCM_E_UNAVAIL;    
    int spl;
    
    uint8 *prior_data;
    
    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, ptp_id, clock_num, 
            PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        PTP_ERROR_FUNC("_bcm_ptp_function_precheck()");
        return rv;   
    }
    
    if ((unit_rx_array[unit].memstate != PTP_MEMSTATE_INITIALIZED) ||
            (unit_rx_array[unit].stack_array[ptp_id].memstate != 
            PTP_MEMSTATE_INITIALIZED)) {
        PTP_ERROR("_bcm_ptp_rx_response_flush(): memory state\n");
        return BCM_E_UNAVAIL;
    }  
    
    if (sal_sem_take(unit_rx_array[unit].stack_array[ptp_id]
            .clock_data[clock_num].response_ready, sal_mutex_NOWAIT) 
            == BCM_E_NONE) {
        /*
         * Flush response.
         * NOTICE: Response already waiting is unexpected.
         */
        PTP_ERROR("Flushed unexpected response\n");
        
        /* Lock. */
        spl = sal_splhi();
        
        prior_data = unit_rx_array[unit].stack_array[ptp_id]
                                        .clock_data[clock_num].response_data;
        
        unit_rx_array[unit].stack_array[ptp_id]
                           .clock_data[clock_num].response_data = 0;
        
        /* Unlock. */
        sal_spl(spl);

        if (prior_data) {
            bcm_rx_free(unit, prior_data);
        }
    }

    return BCM_E_NONE;
}

/*
 * Function:
 *      _bcm_ptp_rx_response_get
 * Purpose:
 *      Get Rx response data for a PTP clock.
 * Parameters:
 *      unit       - (IN)  Unit number.
 *      ptp_id     - (IN)  PTP stack ID.
 *      clock_num  - (IN)  PTP clock number.
 *      usec       - (IN)  Semaphore timeout (usec).
 *      data       - (OUT) Response data.
 *      data_len   - (OUT) Response data size (octets).
 * Returns:
 *      BCM_E_XXX
 * Notes:
 */
int 
_bcm_ptp_rx_response_get(
    int unit,
    bcm_ptp_stack_id_t ptp_id, 
    int clock_num, 
    int usec, 
    uint8 **data, 
    int *data_len)
{
    int rv = BCM_E_UNAVAIL;   
    int spl;
    
    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, ptp_id, clock_num, 
            PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        return rv;   
    }
    
    if ((unit_rx_array[unit].memstate != PTP_MEMSTATE_INITIALIZED) ||
            (unit_rx_array[unit].stack_array[ptp_id].memstate != 
            PTP_MEMSTATE_INITIALIZED)) {
        return BCM_E_UNAVAIL;
    }  
                
    if (BCM_FAILURE(rv = sal_sem_take(unit_rx_array[unit].stack_array[ptp_id]
            .clock_data[clock_num].response_ready, usec))) {
        PTP_ERROR_FUNC("sal_sem_take()");
        return rv;
    }
        
    /* Lock. */
    spl = sal_splhi(); 
    
    *data = unit_rx_array[unit].stack_array[ptp_id]
               .clock_data[clock_num].response_data;
    
    *data_len = unit_rx_array[unit].stack_array[ptp_id]
                    .clock_data[clock_num].response_len;
    
    unit_rx_array[unit].stack_array[ptp_id]
        .clock_data[clock_num].response_data = 0;
    
    /* Unlock. */
    sal_spl(spl);

    return rv;
}

bcm_rx_t 
_bcm_ptp_rx_callback(
    int unit, 
    bcm_pkt_t *pkt, 
    void *cookie)
{   
    int rv = BCM_E_UNAVAIL;
    int spl;
    
    uint8 *prior_data;
    uint16 udp_dest_port;
    uint16 message_len;
    
    _bcmPTPmessageType_t message_type;
    bcm_ptp_protocol_t protocol;
    uint16 src_addr_offset;
    uint16 ptp_offset;
    
    bcm_ptp_clock_identity_t cb_clock_identity;
    int cb_unit;
    bcm_ptp_stack_id_t cb_ptp_id;
    int cb_clock_num;
    uint32 cb_flags;
        
    int i = 0;
    
    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, PTP_STACK_ID_DEFAULT, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        PTP_ERROR_FUNC("_bcm_ptp_function_precheck()");  
        return BCM_RX_NOT_HANDLED;
    }
    
    if (unit_rx_array[unit].memstate != PTP_MEMSTATE_INITIALIZED) {
        PTP_ERROR("Rx unit data not initialized");
        return BCM_RX_NOT_HANDLED;
    }  

#ifdef PTP_RX_CALLBACK_DEBUG
    sal_printf("_bcm_ptp_rx_callback(%d,%d)\n", pkt->pkt_len, BCM_PKT_IEEE_LEN(pkt));
        _bcm_ptp_dump_hex(BCM_PKT_IEEE(pkt), BCM_PKT_IEEE_LEN(pkt));
#endif
    
    if (pkt->pkt_data[0].len < pkt->pkt_len || 
            pkt->pkt_len < PTP_RX_PACKET_MIN_SIZE_OCTETS) {
        /* 
         * Ignore packet. 
         * NOTICE: inconsistent or incompatible packet length. 
         */
        return BCM_RX_NOT_HANDLED;
    }
    
    if (BCM_FAILURE(rv = _bcm_ptp_rx_message_length_get(BCM_PKT_IEEE(pkt),
            &message_len))) {
        PTP_ERROR_FUNC("_bcm_ptp_rx_message_length_get()");
        return BCM_RX_NOT_HANDLED;
    }
        
    /* 
     * Parse packet data.     
     * Get destination port from UDP header, which is a proxy for packet
     * type and subsequent handling. 
     */
    if (BCM_FAILURE(rv = _bcm_ptp_rx_message_destination_port_get(
             BCM_PKT_IEEE(pkt), &udp_dest_port))) {
        PTP_ERROR_FUNC("_bcm_ptp_rx_message_destination_port_get()");
        return BCM_RX_NOT_HANDLED;
    }
    
    switch (udp_dest_port) {
    case (0x0140):
        /* Response message. */
        
        /* 
         * Parse packet data.
         * Lookup the unit number, PTP stack ID, and PTP clock number  
         * association of the packet based on the sender's PTP clock 
         * identity.
         */          
        if (BCM_FAILURE(rv = _bcm_ptp_rx_message_source_clock_identity_get(
                BCM_PKT_IEEE(pkt), &cb_clock_identity))) {
            PTP_ERROR_FUNC("_bcm_ptp_rx_message_source_clock_identity_get()");
            return BCM_RX_NOT_HANDLED;
        }
        if (BCM_FAILURE(rv = _bcm_ptp_clock_lookup(cb_clock_identity, 
                &cb_unit, &cb_ptp_id, &cb_clock_num))) {
            PTP_ERROR_FUNC("_bcm_ptp_clock_lookup()");  
            return BCM_RX_NOT_HANDLED;
        }

        /* 
         * Ensure that Rx framework is initialized and Rx data structures are 
         * created for the requisite unit and PTP stack.
         */
        if ((unit_rx_array[cb_unit].memstate != PTP_MEMSTATE_INITIALIZED) ||
                (unit_rx_array[cb_unit].stack_array[cb_ptp_id].memstate != 
                PTP_MEMSTATE_INITIALIZED)) {
            PTP_ERROR("Rx unit/stack data not initialized\n");
            return BCM_RX_NOT_HANDLED;
        }  
    
        /* Lock. */
        spl = sal_splhi(); 
        
        prior_data = unit_rx_array[cb_unit].stack_array[cb_ptp_id]
                                           .clock_data[cb_clock_num]
                                           .response_data;
        
        unit_rx_array[cb_unit].stack_array[cb_ptp_id]
                              .clock_data[cb_clock_num]
                              .response_data = pkt->pkt_data[0].data;
        
        unit_rx_array[cb_unit].stack_array[cb_ptp_id]
                              .clock_data[cb_clock_num]
                              .response_len =  pkt->pkt_len;
        
        /* Unlock. */
        sal_spl(spl);
        
        /* 
         * Free unclaimed response.
         * NOTICE: If prior data exists, it must be freed.
         */
        if (prior_data) {
            PTP_ERROR("Unclaimed response free'd.\n");
            bcm_rx_free(cb_unit, prior_data);
        }
        
        sal_sem_give(unit_rx_array[cb_unit].stack_array[cb_ptp_id]
                     .clock_data[cb_clock_num].response_ready);
        
        return BCM_RX_HANDLED_OWNED;

        break;
        
    case (0x0141):
        /* Forwarded (tunnel) message. */
        if (message_len < PTP_RX_TUNNEL_MSG_MIN_SIZE_OCTETS) {
            PTP_ERROR("Invalid (too-short) tunnel message received (0x%04x)\n", 
                      message_len);
            return BCM_RX_HANDLED;
        }
        
        /* 
         * Parse wrapping header. 
         * Move cursor forward to "remove" wrapping header.
         *
         * NOTICE: Forwarded tunnel message prepends an 8-element header, which
         *         includes PTP message and addressing metadata.
         *         Wrapping Header Octets 0...1: PTP message type.                   
         *         Wrapping Header Octets 2...3: Protocol.
         *         Wrapping Header Octets 4...5: Source address offset.
         *         Wrapping Header Octets 6...7: PTP payload offset.
         */
        i = PTP_PTPHDR_START_IDX;
        
        message_type = _bcm_ptp_uint16_read(BCM_PKT_IEEE(pkt) + i);
        i += sizeof(uint16);
        message_len -= sizeof(uint16);
        
        protocol = _bcm_ptp_uint16_read(BCM_PKT_IEEE(pkt) + i);
        i += sizeof(uint16);
        message_len -= sizeof(uint16);
        
        src_addr_offset = _bcm_ptp_uint16_read(BCM_PKT_IEEE(pkt) + i);
        i += sizeof(uint16);
        message_len -= sizeof(uint16);
        
        ptp_offset = _bcm_ptp_uint16_read(BCM_PKT_IEEE(pkt) + i);
        i += sizeof(uint16);
        message_len -= sizeof(uint16);
                
        /* 
         * TODO: Handle case where tunneled message is non-IPv4. Alternative 
         *       mechanism req'd to ascertain unit, stack, PTP clock routing?
         *       Add this information to the wrapping header?
         *
         * Parse packet data.
         * Lookup the unit number, PTP stack ID, and PTP clock number  
         * association of the packet based on the recipient's PTP clock 
         * identity.
         */          
        if (BCM_FAILURE(rv = _bcm_ptp_rx_message_target_clock_identity_get(
                BCM_PKT_IEEE(pkt) + i, &cb_clock_identity))) {
            PTP_ERROR_FUNC("_bcm_ptp_rx_message_target_clock_identity_get()");
            return BCM_RX_NOT_HANDLED;
        }
        
        if (BCM_FAILURE(rv = _bcm_ptp_clock_lookup(cb_clock_identity, 
                &cb_unit, &cb_ptp_id, &cb_clock_num))) {
            PTP_ERROR_FUNC("_bcm_ptp_clock_lookup()");  
            return BCM_RX_NOT_HANDLED;
        }

        /* 
         * Ensure that Rx framework is initialized and Rx data structures are 
         * created for the requisite unit and PTP stack.
         */
        if ((unit_rx_array[cb_unit].memstate != PTP_MEMSTATE_INITIALIZED) ||
                (unit_rx_array[cb_unit].stack_array[cb_ptp_id].memstate != 
                PTP_MEMSTATE_INITIALIZED)) {
            PTP_ERROR("Rx unit/stack data not initialized\n");
            return BCM_RX_NOT_HANDLED;
        }          
                
        switch (message_type) {
        case _bcmPTPmessageType_signaling:
            /* TODO: Set flags for PTP signaling message callback. */
            cb_flags = 0;
        
            if (unit_rx_array[unit].signal_cb)
            {
                unit_rx_array[unit].signal_cb(cb_unit, cb_ptp_id, cb_flags, 
                    bcmPTPCallbackTypeSignal, 
                    message_len, BCM_PKT_IEEE(pkt) + i,
                    unit_rx_array[unit].signal_user_data);
            }
            
            break;
            
        case _bcmPTPmessageType_management:
            /* TODO: Set flags for PTP management message callback. */
            cb_flags = 0;
        
            if (unit_rx_array[unit].management_cb) {
                unit_rx_array[unit].management_cb(cb_unit, cb_ptp_id, cb_flags, 
                    bcmPTPCallbackTypeManagement, 
                    message_len, BCM_PKT_IEEE(pkt) + i,
                    unit_rx_array[unit].management_user_data);
            }
            
            break;
        
        default:
            PTP_ERROR("Invalid tunnel message received: "
                      "unknown/unsupported type (0x%02x)\n", message_type);
        }
        
        /* Conditionally tunnel message to ToP. */
        if (rv == PTP_CALLBACK_ACCEPT) {
            if (BCM_FAILURE(rv =_bcm_ptp_tunnel_message_to_top(cb_unit, 
                    cb_ptp_id, message_len, BCM_PKT_IEEE(pkt) + i))) {
                PTP_ERROR_FUNC("_bcm_ptp_tunnel_message_to_top()");
            }
        }
        
        break;
        
    case (0x0142):
        /* Event message. */
        if (message_len < PTP_RX_EVENT_MSG_MIN_SIZE_OCTETS) {
            PTP_ERROR("Invalid (too-short) event message received (0x%04x)\n", 
                      message_len);
            return BCM_RX_HANDLED;
        }
        
        /* 
         * TODO: Remove hardcoded unit and PTP stack ID.
         *       Additional packet parsing to extract information required 
         *       to route information to unit, PTP stack, and PTP clock.
         *       Prepend metadata to event callback data to facilitate?
         */
        cb_unit = unit;
        cb_ptp_id = 0;        
        cb_flags = 0;
        
        if (unit_rx_array[unit].event_cb) {
            i = PTP_PTPHDR_START_IDX;

            unit_rx_array[unit].event_cb(cb_unit, cb_ptp_id, cb_flags, 
                bcmPTPCallbackTypeEvent, 
                message_len, BCM_PKT_IEEE(pkt) + i,
                unit_rx_array[unit].event_user_data);
        }
           
        break;
        
    default:
#ifdef PTP_RX_CALLBACK_DEBUG
        sal_printf("UDP packet dst port 0x%04x not handled\n", udp_dest_port);
#endif
        return BCM_RX_NOT_HANDLED;
    }
    
    return BCM_RX_HANDLED;
}

#if 0 /* Unused. */
/*
 * Function:
 *      _bcm_ptp_rx_message_source_port_get
 * Purpose:
 *      Get source port number in UDP header.
 * Parameters:
 *      message   - (IN)  PTP management message.
 *      src_port  - (OUT) Source port number.
 * Returns:
 *      BCM_E_XXX
 * Notes:
 */
static int 
_bcm_ptp_rx_message_source_port_get(
    uint8 *message, 
    uint16 *src_port)
{
    int i = PTP_UDPHDR_START_IDX;
    
    *src_port = _bcm_ptp_uint16_read(message + i);
    return BCM_E_NONE;
}
#endif /* Unused. */

/*
 * Function:
 *      _bcm_ptp_rx_message_destination_port_get
 * Purpose:
 *      Get destination port number in UDP header.
 * Parameters:
 *      message   - (IN)  PTP management message.
 *      dest_port - (OUT) Destination port number.
 * Returns:
 *      BCM_E_XXX
 * Notes:
 */
static int 
_bcm_ptp_rx_message_destination_port_get(
    uint8 *message, 
    uint16 *dest_port)
{
    int i = PTP_UDPHDR_START_IDX + PTP_UDPHDR_DESTPORT_OFFSET_OCTETS;

    *dest_port = _bcm_ptp_uint16_read(message + i);
    return BCM_E_NONE;
}

#if 0 /* Unused. */
/*
 * Function:
 *      _bcm_ptp_rx_message_type_get
 * Purpose:
 *      Get PTP messageType attribute in PTP common header.
 * Parameters:
 *      message      - (IN)  PTP management message.
 *      message_type - (OUT) PTP message type (messageType).
 * Returns:
 *      BCM_E_XXX
 * Notes:
 */
static int 
_bcm_ptp_rx_message_type_get(
    uint8 *message, 
    _bcmPTPmessageType_t *message_type)
{
    *message_type = (_bcmPTPmessageType_t)message[PTP_PTPHDR_START_IDX];
    return BCM_E_NONE;
}
#endif /* Unused. */

/*
 * Function:
 *      _bcm_ptp_rx_message_source_clock_identity_get
 * Purpose:
 *      Get PTP source clock identity in PTP common header.
 * Parameters:
 *      message        - (IN)  PTP management message.
 *      clock_identity - (OUT) PTP source clock identity.
 * Returns:
 *      BCM_E_XXX
 * Notes:
 */
static int 
_bcm_ptp_rx_message_source_clock_identity_get(
    uint8 *message, 
    bcm_ptp_clock_identity_t *clock_identity)
{
    int i = PTP_PTPHDR_START_IDX + PTP_PTPHDR_SRCPORT_OFFSET_OCTETS;
    
    sal_memcpy(clock_identity, message + i, sizeof(bcm_ptp_clock_identity_t));
    return BCM_E_NONE;
}

/*
 * Function:
 *      _bcm_ptp_rx_message_target_clock_identity_get
 * Purpose:
 *      Get PTP source target identity in PTP management or signaling message.
 * Parameters:
 *      message        - (IN)  PTP management message.
 *      clock_identity - (OUT) PTP source clock identity.
 * Returns:
 *      BCM_E_XXX
 * Notes:
 */
static int 
_bcm_ptp_rx_message_target_clock_identity_get(
    uint8 *message, 
    bcm_ptp_clock_identity_t *clock_identity)
{
    int i = PTP_PTPHDR_START_IDX + PTP_PTPHDR_SIZE_OCTETS;
    
    sal_memcpy(clock_identity, message + i, sizeof(bcm_ptp_clock_identity_t));
    return BCM_E_NONE;
}

/*
 * Function:
 *      _bcm_ptp_rx_message_length_get
 * Purpose:
 *      Get the length of a message.
 * Parameters:
 *      message     - (IN)  PTP management message.
 *      message_len - (OUT) Message length (octets).
 * Returns:
 *      BCM_E_XXX.
 * Notes:
 *      Message length is size of Rx packet excluding headers.
 */
static int 
_bcm_ptp_rx_message_length_get(
    uint8 *message,
    uint16 *message_len)
{
    int i = PTP_UDPHDR_START_IDX + PTP_UDPHDR_MSGLEN_OFFSET_OCTETS;
    
    *message_len = _bcm_ptp_uint16_read(message + i) - PTP_UDPHDR_SIZE_OCTETS;
    return BCM_E_NONE;   
}

/*
 * Function:
 *      _bcm_ptp_register_management_callback
 * Purpose:
 *      Register a management callback function
 * Parameters:
 *      unit - (IN) Unit number.
 *      cb - (IN) A pointer to the callback function to call for the specified PTP events
 *      user_data - (IN) Pointer to user data to supply in the callback
 * Returns:
 *      BCM_E_XXX.
 * Notes:
 *      The unit is already locked by the calling function
 */
int
_bcm_ptp_register_management_callback(
    int unit, 
    bcm_ptp_cb cb, 
    void *user_data)
{
    int rv = BCM_E_UNAVAIL;

    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, PTP_STACK_ID_DEFAULT, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        return rv;
    }

    unit_rx_array[unit].management_cb = cb;
    unit_rx_array[unit].management_user_data = user_data;

    return rv;
}

/*
 * Function:
 *      _bcm_ptp_register_event_callback
 * Purpose:
 *      Register a event callback function
 * Parameters:
 *      unit - (IN) Unit number.
 *      cb - (IN) A pointer to the callback function to call for the specified PTP events
 *      user_data - (IN) Pointer to user data to supply in the callback
 * Returns:
 *      BCM_E_XXX.
 * Notes:
 *      The unit is already locked by the calling function
 */
int
_bcm_ptp_register_event_callback(
    int unit, 
    bcm_ptp_cb cb, 
    void *user_data)
{
    int rv = BCM_E_UNAVAIL;

    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, PTP_STACK_ID_DEFAULT, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        return rv;
    }

    unit_rx_array[unit].event_cb = cb;
    unit_rx_array[unit].event_user_data = user_data;

    return rv;
}

/*
 * Function:
 *      _bcm_ptp_register_signal_callback
 * Purpose:
 *      Register a signal callback function
 * Parameters:
 *      unit - (IN) Unit number.
 *      cb - (IN) A pointer to the callback function to call for the specified PTP events
 *      user_data - (IN) Pointer to user data to supply in the callback
 * Returns:
 *      BCM_E_XXX.
 * Notes:
 *      The unit is already locked by the calling function
 */
int
_bcm_ptp_register_signal_callback(
    int unit, 
    bcm_ptp_cb cb, 
    void *user_data)
{
    int rv = BCM_E_UNAVAIL;

    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, PTP_STACK_ID_DEFAULT, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        return rv;
    }

    unit_rx_array[unit].signal_cb = cb;
    unit_rx_array[unit].signal_user_data = user_data;

    return rv;
}

/*
 * Function:
 *      _bcm_ptp_register_fault_callback
 * Purpose:
 *      Register a fault callback function
 * Parameters:
 *      unit - (IN) Unit number.
 *      cb - (IN) A pointer to the callback function to call for the specified PTP events
 *      user_data - (IN) Pointer to user data to supply in the callback
 * Returns:
 *      BCM_E_XXX.
 * Notes:
 *      The unit is already locked by the calling function
 */
int
_bcm_ptp_register_fault_callback(
    int unit, 
    bcm_ptp_cb cb, 
    void *user_data)
{
    int rv = BCM_E_UNAVAIL;

    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, PTP_STACK_ID_DEFAULT, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        return rv;
    }

    unit_rx_array[unit].fault_cb = cb;
    unit_rx_array[unit].fault_user_data = user_data;

    return rv;
}

/*
 * Function:
 *      _bcm_ptp_unregister_management_callback
 * Purpose:
 *      Unregister a management callback function
 * Parameters:
 *      unit - (IN) Unit number.
 *      cb - (IN) A pointer to the callback function to call for the specified PTP events
 * Returns:
 *      BCM_E_XXX.
 * Notes:
 *      The unit is already locked by the calling function
 */
int
_bcm_ptp_unregister_management_callback(
    int unit)
{
    int rv = BCM_E_UNAVAIL;

    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, PTP_STACK_ID_DEFAULT, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        return rv;
    }

    unit_rx_array[unit].management_cb = NULL;
    unit_rx_array[unit].management_user_data = NULL;

    return rv;
}

/*
 * Function:
 *      _bcm_ptp_unregister_event_callback
 * Purpose:
 *      Unregister a event callback function
 * Parameters:
 *      unit - (IN) Unit number.
 *      cb - (IN) A pointer to the callback function to call for the specified PTP events
 *      user_data - (IN) Pointer to user data to supply in the callback
 * Returns:
 *      BCM_E_XXX.
 * Notes:
 *      The unit is already locked by the calling function
 */
int
_bcm_ptp_unregister_event_callback(
    int unit)
{
    int rv = BCM_E_UNAVAIL;

    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, PTP_STACK_ID_DEFAULT, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        return rv;
    }

    unit_rx_array[unit].event_cb = NULL;
    unit_rx_array[unit].event_user_data = NULL;

    return rv;
}

/*
 * Function:
 *      _bcm_ptp_unregister_signal_callback
 * Purpose:
 *      Unregister a signal callback function
 * Parameters:
 *      unit - (IN) Unit number.
 *      cb - (IN) A pointer to the callback function to call for the specified PTP events
 *      user_data - (IN) Pointer to user data to supply in the callback
 * Returns:
 *      BCM_E_XXX.
 * Notes:
 *      The unit is already locked by the calling function
 */
int
_bcm_ptp_unregister_signal_callback(
    int unit)
{
    int rv = BCM_E_UNAVAIL;

    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, PTP_STACK_ID_DEFAULT, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        return rv;
    }

    unit_rx_array[unit].signal_cb = NULL;
    unit_rx_array[unit].signal_user_data = NULL;

    return rv;
}

/*
 * Function:
 *      _bcm_ptp_unregister_fault_callback
 * Purpose:
 *      Unregister a fault callback function
 * Parameters:
 *      unit - (IN) Unit number.
 *      cb - (IN) A pointer to the callback function to call for the specified PTP events
 *      user_data - (IN) Pointer to user data to supply in the callback
 * Returns:
 *      BCM_E_XXX.
 * Notes:
 *      The unit is already locked by the calling function
 */
int
_bcm_ptp_unregister_fault_callback(
    int unit)
{
    int rv = BCM_E_UNAVAIL;

    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, PTP_STACK_ID_DEFAULT, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        return rv;
    }

    unit_rx_array[unit].fault_cb = NULL;
    unit_rx_array[unit].fault_user_data = NULL;

    return rv;
}

/*
 * Function:
 *      _bcm_ptp_event_handler_default
 * Purpose:
 *      Default event callback handler.
 * Parameters:
 *      unit      - (IN) Unit number.
 *      ptp_id    - (IN) PTP stack ID.
 *      flags     - (IN) Callback function flags.
 *      type      - (IN) Callback function type.
 *      length    - (IN) Callback data length (octets).
 *      data      - (IN) Callback data.
 *      user_data - (IN) Callback user data.
 * Returns:
 *      BCM_E_XXX
 * Notes:
 */
STATIC int
_bcm_ptp_event_handler_default(
    int unit,
    bcm_ptp_stack_id_t ptp_id,
    uint32 flags,
    bcm_ptp_cb_type_t type,
    uint32 length,
    uint8 *data,
    void *user_data)
{
    int rv = BCM_E_UNAVAIL;  
        
    uint8 verbose = 1; 
    uint16 event_type;
    bcm_ptp_protocol_t ucm_protocol;
    
    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, ptp_id, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        PTP_ERROR_FUNC("_bcm_ptp_function_precheck()");
        return rv;   
    }
    
    sal_printf("Event callback (Unit = %d, PTP Stack = %d)\n", unit, ptp_id);
    _bcm_ptp_dump_hex(data, length);
   
    /* Extract event type. */
    event_type = _bcm_ptp_uint16_read(data);
    
    /* Move cursor to beginning of event data. */
    data = data + sizeof(uint16);

    switch ((_bcm_ptp_event_t)event_type) {
    case _bcm_ptp_state_change_event:
        if (verbose) {
            sal_printf("Event: STATE CHANGE\n");
            /* 
             * Event message data.
             *    Octet 0      : Clock instance.
             *    Octet 1...2  : Port number.
             *    Octet 3...12 : Port identity.
             *    Octet 13     : Port state.
             *    Octet 14     : Prior port state.
             *    Octet 15     : Port state change reason.
             */
            sal_printf("   Instance     : %d\n", data[0]);
            sal_printf("   Port Number  : %d\n", _bcm_ptp_uint16_read(data+1));
            sal_printf("   Port Identity: "
                       "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%04x\n",
                       data[3], data[4], data[5], data[6], 
                       data[7], data[8], data[9], data[10],
                       _bcm_ptp_uint16_read(data + 3 + 
                                            sizeof(bcm_ptp_clock_identity_t)));
            sal_printf("----------------------------------------"
                       "----------------------------------------\n");
            sal_printf("   Old Port State: %d (%s)\n", data[14], 
                       _bcm_ptp_port_state_description(data[14]));
            sal_printf("   New Port State: %d (%s)\n", data[13], 
                       _bcm_ptp_port_state_description(data[13]));
            
            switch (data[15]) 
            {
            case _bcm_ptp_state_change_reason_startup:
                sal_printf("   Reason        : %u "
                           "(Startup, instance creation).\n", data[15]);
                break;
                
            case _bcm_ptp_state_change_reason_port_init:
                sal_printf("   Reason        : %u "
                           "(Port initialization).\n", data[15]);
                break;
                
            case _bcm_ptp_state_change_reason_fault:
                sal_printf("   Reason        : %u "
                           "(Fault detected).\n", data[15]);
                break;
                
            case _bcm_ptp_state_change_reason_bmca:
                sal_printf("   Reason        : %u "
                           "(BMCA state transition).\n", data[15]);
                break;
                
            case _bcm_ptp_state_change_reason_mnt_enable:
                sal_printf("   Reason        : %u "
                           "(Enable port management message).\n", data[15]);
                break;
                
            case _bcm_ptp_state_change_reason_mnt_disable:
                sal_printf("   Reason        : %u "
                           "(Disable port management message).\n", data[15]);
                break;
                
            case _bcm_ptp_state_change_reason_netw_reinit:
                sal_printf("   Reason        : %u "
                           "(Network interface re-initialization).\n", data[15]);
                break;
                
            case _bcm_ptp_state_change_reason_dt_master_slave:
                sal_printf("   Reason        : %u "
                           "(Timestamp difference, master-to-slave).\n", data[15]);
                break;
                
            default:
                sal_printf("   Reason        : %u "
                           "(Unknown).\n", data[15]);
            }
        }
        break;

    case _bcm_ptp_master_change_event:
        if (verbose) {
            sal_printf("Event: MASTER CHANGE\n");
            /* 
             * Event message data.
             *    Octet 0       : Clock instance.
             *    Octet 1...2   : Port number.
             *    Octet 3...12  : Port identity.
             *    Octet 13...22 : New master port identity.
             *    Octet 23      : New master is-unicast Boolean.
             *    Octet 24      : New master network protocol (unicast master).
             *    Octet 25...26 : New master port address length (unicast master).
             *    Octet 27...42 : New master port address (unicast master).
             *    Octet 43...52 : Old master port identity.
             *    Octet 53      : Old master is-unicast Boolean.
             *    Octet 54      : Old master network protocol (unicast master).
             *    Octet 55...56 : Old master port address length (unicast master).
             *    Octet 57...72 : Old master port address (unicast master).
             */
            sal_printf("   Instance     : %d\n", data[0]);
            sal_printf("   Port Number  : %d\n", _bcm_ptp_uint16_read(data+1));
            sal_printf("   Port Identity: "
                       "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%04x\n",
                       data[3], data[4], data[5], data[6], 
                       data[7], data[8], data[9], data[10],
                       _bcm_ptp_uint16_read(data + 3 + 
                                            sizeof(bcm_ptp_clock_identity_t)));
            sal_printf("----------------------------------------"
                       "----------------------------------------\n");
            sal_printf("   New Master Properties\n");
            sal_printf("   Port Identity     : "
                       "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%04x\n",
                       data[13], data[14], data[15], data[16], 
                       data[17], data[18], data[19], data[20],
                       _bcm_ptp_uint16_read(data + 13 + 
                                            sizeof(bcm_ptp_clock_identity_t)));
            sal_printf("   Unicast Master    : %u\n", data[23]);
            if (data[23]) {
                ucm_protocol = data[24];
               
                sal_printf("   UC Master Protocol: %u", ucm_protocol);   
                if (ucm_protocol == bcmPTPIEEE8023) {
                    sal_printf(" (Ethernet Layer 2)\n");
                } else if (ucm_protocol == bcmPTPUDPIPv4) {
                    sal_printf(" (Ethernet/UDP/IPv4)\n");
                    sal_printf("   UC Master Address : %u.%u.%u.%u (IPv4)\n", 
                               data[27], data[28], data[29], data[30]);
                } else if (ucm_protocol == bcmPTPUDPIPv6){
                    sal_printf(" (Ethernet/UDP/IPv6)\n");
                    sal_printf("   UC Master Address : "
                               "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
                               "%02x%02x:%02x%02x:%02x%02x:%02x%02x (IPv6)\n",
                               data[27], data[28], data[29], data[30],
                               data[31], data[32], data[33], data[34],
                               data[35], data[36], data[37], data[38],
                               data[39], data[40], data[41], data[42]);
                } else {
                    sal_printf(" (Unknown)\n");
                }
            }
            
            sal_printf("\n");
            sal_printf("   Old Master Properties\n");
            sal_printf("   Port Identity     : "
                       "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%04x\n",
                       data[43], data[44], data[45], data[46], 
                       data[47], data[48], data[49], data[50],
                       _bcm_ptp_uint16_read(data + 43 + 
                                            sizeof(bcm_ptp_clock_identity_t)));
            sal_printf("   Unicast Master    : %u\n", data[53]);
            if (data[53]) {
                ucm_protocol = data[54];
               
                sal_printf("   UC Master Protocol: %u", ucm_protocol);   
                if (ucm_protocol == bcmPTPIEEE8023) {
                    sal_printf(" (Ethernet Layer 2)\n");
                } else if (ucm_protocol == bcmPTPUDPIPv4) {
                    sal_printf(" (Ethernet/UDP/IPv4)\n");
                    sal_printf("   UC Master Address : %u.%u.%u.%u (IPv4)\n", 
                               data[57], data[58], data[59], data[60]);
                } else if (ucm_protocol == bcmPTPUDPIPv6) {
                    sal_printf(" (Ethernet/UDP/IPv6)\n");
                    sal_printf("   UC Master Address : "
                               "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
                               "%02x%02x:%02x%02x:%02x%02x:%02x%02x (IPv6)\n",
                               data[57], data[58], data[59], data[60],
                               data[61], data[62], data[63], data[64],
                               data[65], data[66], data[67], data[68],
                               data[69], data[70], data[71], data[72]);
                } else {
                    sal_printf(" (Unknown)\n");
                }
            }
        } 
        break;        

    case _bcm_ptp_master_avail_event:
        if (verbose) {
            sal_printf("Event: MASTER AVAILABLE\n");
            /* 
             * Event message data.
             *    Octet 0       : Clock instance.
             *    Octet 1...2   : Port number. 
             *    Octet 3...12  : Port identity.
             *    Octet 13...22 : Foreign master port identity.
             *    Octet 23      : Foreign master is-acceptable Boolean.
             *    Octet 24      : Foreign master is-unicast Boolean.
             *    Octet 25      : Foreign master network protocol (unicast foreign master).
             *    Octet 26...27 : Foreign master port address length (unicast foreign master).
             *    Octet 28...43 : Foreign master port address (unicast foreign master).
             */
            sal_printf("   Instance     : %d\n", data[0]);
            sal_printf("   Port Number  : %d\n", _bcm_ptp_uint16_read(data+1));
            sal_printf("   Port Identity: "
                       "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%04x\n",
                       data[3], data[4], data[5], data[6], 
                       data[7], data[8], data[9], data[10],
                       _bcm_ptp_uint16_read(data + 3 + 
                                            sizeof(bcm_ptp_clock_identity_t)));
            sal_printf("----------------------------------------"
                       "----------------------------------------\n");
            sal_printf("   Foreign Master Properties\n");
            sal_printf("   Port Identity     : "
                       "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%04x\n",
                       data[13], data[14], data[15], data[16], 
                       data[17], data[18], data[19], data[20],
                       _bcm_ptp_uint16_read(data + 13 + 
                                            sizeof(bcm_ptp_clock_identity_t)));
            sal_printf("   Acceptable Master : %u\n", data[23]);
            sal_printf("   Unicast Master    : %u\n", data[24]);
            if (data[24]) {
                ucm_protocol = data[25];
               
                sal_printf("   UC Master Protocol: %u", ucm_protocol);   
                if (ucm_protocol == bcmPTPIEEE8023) {
                    sal_printf(" (Ethernet Layer 2)\n");
                } else if (ucm_protocol == bcmPTPUDPIPv4) {
                    sal_printf(" (Ethernet/UDP/IPv4)\n");
                    sal_printf("   UC Master Address : %u.%u.%u.%u (IPv4)\n", 
                               data[28], data[29], data[30], data[31]);
                } else if (ucm_protocol== bcmPTPUDPIPv6) {
                    sal_printf(" (Ethernet/UDP/IPv6)\n");
                    sal_printf("   UC Master Address : "
                               "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
                               "%02x%02x:%02x%02x:%02x%02x:%02x%02x (IPv6)\n",
                               data[28], data[29], data[30], data[31],
                               data[32], data[33], data[34], data[35],
                               data[36], data[37], data[38], data[39],
                               data[40], data[41], data[42], data[43]);
                } else {
                    sal_printf(" (Unknown)\n");
                }
            }
        }
        break;

    case _bcm_ptp_master_unavail_event:
        if (verbose) {
            sal_printf("Event: MASTER UNAVAILABLE\n");
            /*
             * Event message data.
             *    Octet 0     : Clock instance.
             *    Octet 1...2 : Port number.
             *    Octet 3...12: Port identity.
             *    Octet 13...22: Foreign master port identity.
             *    Octet 23     : Foreign master is-acceptable Boolean.
             *    Octet 24     : Foreign master is-unicast Boolean.
             *    Octet 25     : Foreign master network protocol (unicast foreign master).
             *    Octet 26...27: Foreign master port address length (unicast foreign master).
             *    Octet 28...43: Foreign master port address (unicast foreign master).
             */
            sal_printf("   Instance     : %d\n", data[0]);
            sal_printf("   Port Number  : %d\n", _bcm_ptp_uint16_read(data+1));
            sal_printf("   Port Identity: "
                       "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%04x\n",
                       data[3], data[4], data[5], data[6], 
                       data[7], data[8], data[9], data[10],
                       _bcm_ptp_uint16_read(data + 3 + 
                                            sizeof(bcm_ptp_clock_identity_t)));
            sal_printf("----------------------------------------"
                       "----------------------------------------\n");
            sal_printf("   Foreign Master Properties\n");
            sal_printf("   Port Identity     : "
                       "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%04x\n",
                       data[13], data[14], data[15], data[16], 
                       data[17], data[18], data[19], data[20],
                       _bcm_ptp_uint16_read(data + 13 + 
                                            sizeof(bcm_ptp_clock_identity_t)));
            sal_printf("   Acceptable Master : %u\n", data[23]);
            sal_printf("   Unicast Master    : %u\n", data[24]);
            if (data[24]) {
                ucm_protocol = data[25];
               
                sal_printf("   UC Master Protocol: %u", ucm_protocol);   
                if (ucm_protocol == bcmPTPIEEE8023) {
                    sal_printf(" (Ethernet Layer 2)\n");
                } else if (ucm_protocol == bcmPTPUDPIPv4) {
                    sal_printf(" (Ethernet/UDP/IPv4)\n");
                    sal_printf("   UC Master Address : %u.%u.%u.%u (IPv4)\n", 
                               data[28], data[29], data[30], data[31]);
                } else if (ucm_protocol == bcmPTPUDPIPv6) {
                    sal_printf(" (Ethernet/UDP/IPv6)\n");
                    sal_printf("   UC Master Address : "
                               "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
                               "%02x%02x:%02x%02x:%02x%02x:%02x%02x (IPv6)\n",
                               data[28], data[29], data[30], data[31],
                               data[32], data[33], data[34], data[35],
                               data[36], data[37], data[38], data[39],
                               data[40], data[41], data[42], data[43]);
                } else {
                    sal_printf(" (Unknown)\n");
                }
            }
        }
        break;

    case _bcm_ptp_slave_avail_event:
        break;
        
    case _bcm_ptp_slave_unavail_event:
        break;

    case _bcm_ptp_top_oom_event:
        if (verbose) {
            /* 
             * TODO: Requires periodic check of PCI shared data interface in
             *       the ToP OOM manager function, which has previously been 
             *       implemented via piggyback on fault callback function. 
             */
            
            sal_printf("Event: ToP OUT-OF-MEMORY\n");
            /*
             * Event message data.
             *    Octet 0...3 : Minimum free memory (bytes).
             *    Octet 4...7 : Free ordinary blocks (bytes).
             */
            sal_printf("   Min. Free Memory: %u (bytes)\n", 
                       _bcm_ptp_uint32_read(data));
            sal_printf("   Ord. Blocks Free: %u (bytes)\n", 
                       _bcm_ptp_uint32_read(data + sizeof(uint32)));
        }
        break;

    case _bcm_ptp_top_watchdog_event:
        break;

    case _bcm_ptp_top_ready_event:
        if (verbose) {
            sal_printf("Event: ToP READY\n");
        }
        break;

    case _bcm_ptp_top_misc_event:
        if (verbose) {
            sal_printf("Event: ToP MISC\n");
        }
        break;
        
    case _bcm_ptp_top_tod_avail_event:
        if (verbose) {
            sal_printf("Event: ToP ToD Available\n");
        }
        break;
    
    case _bcm_ptp_top_tod_unavail_event:
        if (verbose) {
            sal_printf("Event: ToP ToD Unavailable\n");
        }
        break;
        
    case _bcm_ptp_ieee1588_warn_event:
        if (verbose) {
            sal_printf("Event: IEEE Std. 1588-2008 WARNING\n");
            /* 
             * Event message data.
             *    Octet 0      : Clock instance.
             *    Octet 1...2  : Port number.
             *    Octet 3...12 : Port identity.
             *    Octet 13     : IEEE Std. 1588-2008 warning reason code.
             *    Octet 14...N : Reason-dependent message data.
             */
            sal_printf("   Instance     : %d\n", data[0]);
            sal_printf("   Port Number  : %d\n", _bcm_ptp_uint16_read(data+1));
            sal_printf("   Port Identity: "
                       "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%04x\n",
                       data[3], data[4], data[5], data[6], 
                       data[7], data[8], data[9], data[10],
                       _bcm_ptp_uint16_read(data + 3 + 
                                            sizeof(bcm_ptp_clock_identity_t)));
            sal_printf("----------------------------------------"
                       "----------------------------------------\n");
            sal_printf("   Warning Reason: %u (%s)\n", data[13], 
                       _bcm_ptp_ieee1588_warn_reason_description(data[13]));
            sal_printf("\n");
            
            switch (data[13]) {
            case _bcm_ptp_ieee1588_warn_reason_logAnnounceInterval:
                /*
                 * Non-uniform logAnnounceInterval in a PTP domain.
                 *    Octet 0...13  : IEEE Std. 1588-2008 warning common data.
                 *    Octet 14...23 : Foreign master port identity.
                 *    Octet 24      : Foreign master is-acceptable Boolean.
                 *    Octet 25      : Foreign master is-unicast Boolean.
                 *    Octet 26      : Foreign master network protocol (unicast foreign master).
                 *    Octet 27...28 : Foreign master port address length (unicast foreign master).
                 *    Octet 29...44 : Foreign master port address (unicast foreign master).                
                 *    Octet 45      : Foreign master logAnnounceInterval.
                 */
                sal_printf("   Foreign Master Properties\n");
                sal_printf("   Port Identity      : "
                           "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%04x\n",
                           data[14], data[15], data[16], data[17], 
                           data[18], data[19], data[20], data[21],
                           _bcm_ptp_uint16_read(data + 14 + 
                                                sizeof(bcm_ptp_clock_identity_t)));
                sal_printf("   Acceptable Master  : %u\n", data[24]);
                sal_printf("   Unicast Master     : %u\n", data[25]);
                
                if (data[25]) {
                    ucm_protocol = data[26];

                   sal_printf("   UC Master Protocol: %u", ucm_protocol);   
                   if (ucm_protocol == bcmPTPIEEE8023) {
                        sal_printf(" (Ethernet Layer 2)\n");
                    } else if (ucm_protocol == bcmPTPUDPIPv4) {
                        sal_printf(" (Ethernet/UDP/IPv4)\n");
                        sal_printf("   UC Master Address  : %u.%u.%u.%u (IPv4)\n", 
                                   data[29], data[30], data[31], data[32]);
                    } else if (ucm_protocol == bcmPTPUDPIPv6) {
                        sal_printf(" (Ethernet/UDP/IPv6)\n");
                        sal_printf("   UC Master Address  : "
                                   "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
                                   "%02x%02x:%02x%02x:%02x%02x:%02x%02x (IPv6)\n",
                                   data[29], data[30], data[31], data[32],
                                   data[33], data[34], data[35], data[36],
                                   data[37], data[38], data[39], data[40],
                                   data[41], data[42], data[43], data[44]);
                    } else {
                        sal_printf("   UC Master Protocol : %u (Unknown)\n", ucm_protocol);
                    }
                }
                sal_printf("   logAnnounceInterval: %d\n", (int8)data[45]);
                break;
                
            default:       
              ;       
            }
        }
        break;
            
    case _bcm_ptp_servo_state_event:
        sal_printf("Event: SERVO STATE\n");
        sal_printf("   Instance  : %d\n", data[0]);
        sal_printf("   Old State : %d (%s)\n", data[2], 
                   _bcm_ptp_servo_state_description(data[2]));
        sal_printf("   New State : %d (%s)\n", data[1], 
                   _bcm_ptp_servo_state_description(data[1]));
        
        break;

    case _bcm_ptp_pps_in_state_event:
        sal_printf("Event: PPS-IN STATE\n");
        sal_printf("   State : %d (%s)\n", data[0], 
                   _bcm_ptp_pps_in_state_description(data[0]));

        break;

    default:
        sal_printf("Unexpected event, type %d\n", event_type);

        break;
    }
    
    /* Rewind cursor. */
    data = data - sizeof(uint16);
    
    return rv;
}

/*
 * Function:
 *      _bcm_ptp_management_handler_default
 * Purpose:
 *      Default forwarded (tunneled) PTP management message callback handler.
 * Parameters:
 *      unit      - (IN) Unit number.
 *      ptp_id    - (IN) PTP stack ID.
 *      flags     - (IN) Callback function flags.
 *      type      - (IN) Callback function type.
 *      length    - (IN) Callback data length (octets).
 *      data      - (IN) Callback data.
 *      user_data - (IN) Callback user data.
 * Returns:
 *      BCM_E_XXX (if failure)
 *      PTP_CALLBACK_ACCEPT (if success)
 * Notes:
 */
STATIC int
_bcm_ptp_management_handler_default(
    int unit,
    bcm_ptp_stack_id_t ptp_id,
    uint32 flags,
    bcm_ptp_cb_type_t type,
    uint32 length,
    uint8 *data,
    void *user_data)
{
    int rv = BCM_E_UNAVAIL;  
        
    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, ptp_id, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        PTP_ERROR_FUNC("_bcm_ptp_function_precheck()");
        return rv;   
    }
    
    sal_printf("Management callback (Unit = %d, PTP Stack = %d)\n", unit, ptp_id);
    _bcm_ptp_dump_hex(data, length);
        
    return PTP_CALLBACK_ACCEPT;
}

/*
 * Function:
 *      _bcm_ptp_signal_handler_default
 * Purpose:
 *      Default forwarded (tunneled) PTP signaling message callback handler.
 * Parameters:
 *      unit      - (IN) Unit number.
 *      ptp_id    - (IN) PTP stack ID.
 *      flags     - (IN) Callback function flags.
 *      type      - (IN) Callback function type.
 *      length    - (IN) Callback data length (octets).
 *      data      - (IN) Callback data.
 *      user_data - (IN) Callback user data.
 * Returns:
 *      BCM_E_XXX (if failure)
 *      PTP_CALLBACK_ACCEPT (if success)
 * Notes:
 */
STATIC int
_bcm_ptp_signal_handler_default(
    int unit,
    bcm_ptp_stack_id_t ptp_id,
    uint32 flags,
    bcm_ptp_cb_type_t type,
    uint32 length,
    uint8 *data,
    void *user_data)
{
    int rv = BCM_E_UNAVAIL;  
        
    if (BCM_FAILURE(rv = _bcm_ptp_function_precheck(unit, ptp_id, 
            PTP_CLOCK_NUMBER_DEFAULT, PTP_CLOCK_PORT_NUMBER_DEFAULT))) {
        PTP_ERROR_FUNC("_bcm_ptp_function_precheck()");
        return rv;   
    }
    
    sal_printf("Signaling callback (Unit = %d, PTP Stack = %d)\n", unit, ptp_id);
    _bcm_ptp_dump_hex(data, length);
        
    return PTP_CALLBACK_ACCEPT;
}

/*
 * Function:
 *      _bcm_ptp_port_state_description
 * Purpose:
 *      Interpret PTP port state values.
 * Parameters:
 *      state - (IN) PTP port state code.
 * Returns:
 *      Port state description
 * Notes:
 *      Ref. IEEE Std. 1588-2008, Chapter 8.2.5.3.1, Table 8.
 */
STATIC const char* 
_bcm_ptp_port_state_description(
    _bcm_ptp_port_state_t state)
{
    switch(state) {
    case _bcm_ptp_state_initializing: 
        return "Init";
        
    case _bcm_ptp_state_faulty: 
        return "Faulty";
        
    case _bcm_ptp_state_disabled: 
        return "Disabled";
        
    case _bcm_ptp_state_listening: 
        return "Listening";
        
    case _bcm_ptp_state_pre_master: 
        return "Pre-Master";
        
    case _bcm_ptp_state_master: 
        return "Master";
        
    case _bcm_ptp_state_passive: 
        return "Passive";
        
    case _bcm_ptp_state_uncalibrated: 
        return "Uncalibrated";
        
    case _bcm_ptp_state_slave: 
        return "Slave";
        
    default: 
        return "<invalid>";
    }
}

/*
 * Function:
 *      _bcm_ptp_ieee1588_warn_reason_description
 * Purpose:
 *      Interpret IEEE Std. 1588-2008 warning values.
 * Parameters:
 *      reason - (IN) IEEE Std. 1588-2008 warning code.
 * Returns:
 *      Warning reason.
 * Notes:
 *      Function provides an implementation specific set of warnings
 *      relevant to the IEEE Std. 1588-2008.
 */
STATIC const char* 
_bcm_ptp_ieee1588_warn_reason_description(
    _bcm_ptp_ieee1588_warn_reason_t reason)
{
   switch(reason) {
   case _bcm_ptp_ieee1588_warn_reason_logAnnounceInterval: 
       return "Non-uniform logAnnounceInterval in PTP domain";
       
   default: 
       return "<invalid>";
   }
}

/*
 * Function:
 *      _bcm_ptp_servo_state_description
 * Purpose:
 *      Interpret servo state values.
 * Parameters:
 *      state - (IN) Servo state code.
 * Returns:
 *      Servo state description
 * Notes:
 */
STATIC const char* 
_bcm_ptp_servo_state_description(
    _bcm_ptp_fll_state_t state) 
{
    switch (state) {
    case _bcm_ptp_fll_state_acquiring:
        return "Acquiring Lock";

    case _bcm_ptp_fll_state_warmup:
        return "Warmup";

    case _bcm_ptp_fll_state_fast:
        return "Fast Loop";

    case _bcm_ptp_fll_state_normal:
        return "Normal Loop";

    case _bcm_ptp_fll_state_bridge:
        return "Bridge";

    case _bcm_ptp_fll_state_holdover:
        return "Holdover";

    default:
        return "<Unknown>";
    }
}

/*
 * Function:
 *      _bcm_ptp_pps_in_state_description
 * Purpose:
 *      Interpret PPS-in state values.
 * Parameters:
 *      state - (IN) PPS-in state code.
 * Returns:
 *      PPS-in state description
 * Notes:
 */
STATIC const char* 
_bcm_ptp_pps_in_state_description(
    _bcm_ptp_pps_in_state_t state) 
{
    switch (state) {
    case _bcm_ptp_pps_in_state_missing:
        return "No PPS IN";

    case _bcm_ptp_pps_in_state_active_missing_tod:
        return "PPS IN, but no valid ToD";

    case _bcm_ptp_pps_in_state_valid:
        return "PPS IN with valid ToD";
        
    default:
        return "<Unknown>";
    }
}
#endif /* defined(INCLUDE_PTP) */
